{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "data_file = 'ratings.csv'\n",
    "\n",
    "\n",
    "class ItemCF:\n",
    "    def __init__(self):\n",
    "        self.data = pd.read_csv(data_file, usecols=range(3))\n",
    "        self.data.columns = ['user', 'item', 'rating']\n",
    "        self.train = {}\n",
    "        self.test = {}\n",
    "        self.similarity = {}\n",
    "\n",
    "    @staticmethod\n",
    "    def _process_data(input_data):\n",
    "        \"\"\"\n",
    "        自定义数据处理函数\n",
    "        :param input_data: DataFrame\n",
    "        :return: dict{user_id: {item_id: rating}}\n",
    "        \"\"\"\n",
    "        output_data = {}\n",
    "        for _, items in input_data.iterrows():\n",
    "            user = int(items['user'])\n",
    "            item = int(items['item'])\n",
    "            rating = float(items['rating'])\n",
    "            if user in output_data.keys():\n",
    "                currentRatings = output_data[user]\n",
    "            else:\n",
    "                currentRatings = {}\n",
    "            currentRatings[item] = rating\n",
    "            output_data[user] = currentRatings\n",
    "        return output_data\n",
    "\n",
    "    def load_data(self, train_size):\n",
    "        \"\"\"\n",
    "        加载数据，划分训练集和测试集\n",
    "        :param train_size:\n",
    "        :return:\n",
    "        \"\"\"\n",
    "        train_data = self.data.sample(frac=train_size, random_state=10, axis=0)\n",
    "        test_data = self.data[~self.data.index.isin(train_data.index)]\n",
    "\n",
    "        self.train = self._process_data(train_data)\n",
    "        self.test = self._process_data(test_data)\n",
    "\n",
    "        print('loaded data finish...')\n",
    "\n",
    "    def items_similarity(self, normalize=True):\n",
    "        \"\"\"\n",
    "        计算物品和物品之间的相似度矩阵\n",
    "        :return:\n",
    "        \"\"\"\n",
    "        print('computing all deviations...')\n",
    "        items_matrix = {}\n",
    "        user_items_count = {}\n",
    "        for user, items in self.train.items():\n",
    "            for i in items.keys():\n",
    "                user_items_count.setdefault(i, 0)  # 初始化i行j列的值\n",
    "                user_items_count[i] += 1\n",
    "                items_matrix.setdefault(i, {})\n",
    "                for j in items.keys():\n",
    "                    if i == j:\n",
    "                        continue\n",
    "                    items_matrix[i].setdefault(j, 0)\n",
    "                    # items_matrix[i][j] += 1\n",
    "                    items_matrix[i][j] += 1 / np.math.log(1 + len(items) * 1.0)\n",
    "\n",
    "        if not normalize:\n",
    "            for i, related_items in items_matrix.items():  # 计算相似度矩阵\n",
    "                self.similarity.setdefault(i, {})\n",
    "                for j, rating in related_items.items():\n",
    "                    self.similarity[i][j] = rating / np.sqrt(\n",
    "                        user_items_count[i] * user_items_count[j] * 1.0)  # 余弦相似度\n",
    "        else:\n",
    "            self.similarity_max = {}\n",
    "            for i, related_items in items_matrix.items():\n",
    "                self.similarity.setdefault(i, {})\n",
    "                for j, rating in related_items.items():\n",
    "                    self.similarity_max.setdefault(j, 0)\n",
    "                    self.similarity[i][j] = rating / np.sqrt(\n",
    "                        user_items_count[i] * user_items_count[j] * 1.0)\n",
    "                    if self.similarity[i][j] > self.similarity_max[j]:\n",
    "                        self.similarity_max[j] = self.similarity[i][j]  # 记录第j列的最大值\n",
    "            for i, related_items in items_matrix.items():\n",
    "                for j, rating in related_items.items():\n",
    "                    self.similarity[i][j] = self.similarity[i][j] / self.similarity_max[j]\n",
    "\n",
    "        print('computed all deviations finish...')\n",
    "\n",
    "    def predict(self, user, top_n=None):\n",
    "        \"\"\"\n",
    "        对输入的一个user进行推荐\n",
    "        :param top_n:\n",
    "        :param user:\n",
    "        :return:\n",
    "        \"\"\"\n",
    "        recommendations = {}\n",
    "        if user not in list(self.train.keys()) or user not in list(self.similarity.keys()):  # 训练集中不存在用户返回空结果\n",
    "            return recommendations\n",
    "\n",
    "        action_item = self.train[user]\n",
    "        for sim_item, similarity_factor1 in action_item.items():\n",
    "            for related_item, similarity_factor2 in self.similarity[sim_item].items():\n",
    "                if related_item in action_item.keys():\n",
    "                    continue\n",
    "                recommendations.setdefault(related_item, 0)\n",
    "                recommendations[related_item] += similarity_factor1 * similarity_factor2\n",
    "\n",
    "        result_list = [(item, rating) for item, rating in recommendations.items()]\n",
    "        result_list.sort(key=lambda x: x[1], reverse=True)\n",
    "        if top_n:\n",
    "            top_n = int(top_n)\n",
    "            return result_list[:top_n]\n",
    "        else:\n",
    "            return result_list\n",
    "\n",
    "    def validate(self):\n",
    "        \"\"\"\n",
    "        计算MAE、RMSE评估指标\n",
    "        :return:\n",
    "        \"\"\"\n",
    "        print('calculating MAE and RMSE...')\n",
    "        error_sum = 0.0\n",
    "        sqrError_sum = 0.0\n",
    "        setSum = 0\n",
    "        i = 0\n",
    "        for user in self.test:\n",
    "            i += 1\n",
    "            if i % 100 == 0:\n",
    "                print('calculating %d users' % i)\n",
    "            recommendation = self.predict(user)\n",
    "            userRatings = self.test[user]\n",
    "            for each in recommendation:\n",
    "                item = each[0]\n",
    "                rating = each[1]\n",
    "                if item in userRatings:\n",
    "                    error_sum += abs(userRatings[item] - rating)\n",
    "                    sqrError_sum += (userRatings[item] - rating) ** 2\n",
    "                    setSum += 1\n",
    "        mae = error_sum / setSum\n",
    "        rmse = np.sqrt(sqrError_sum / setSum)\n",
    "\n",
    "        return mae, rmse\n",
    "\n",
    "    def evaluate(self):\n",
    "        \"\"\"\n",
    "        推荐topN结果评估，计算precision和recall\n",
    "        :return:\n",
    "        \"\"\"\n",
    "        hit = 0\n",
    "        recall_sum = 0\n",
    "        precision_sum = 0\n",
    "        i = 0\n",
    "        for user in self.test.keys():\n",
    "            i += 1\n",
    "            if i % 100 == 0:\n",
    "                print('calculating %d users' % i)\n",
    "            real_items = self.test.get(user)  # 测试集真实结果\n",
    "            recommendation = self.predict(user, top_n=10)\n",
    "            rec_result = [(item, rating) for item, rating in recommendation]\n",
    "            pred_items = [p[0] for p in rec_result]  # 预测结果\n",
    "            hit = len([h for h in pred_items if h in real_items])\n",
    "            recall_sum += len(real_items)\n",
    "            precision_sum += len(pred_items)\n",
    "\n",
    "        # print(precision_sum, recall_sum)\n",
    "        precision = hit / (precision_sum * 1.0)\n",
    "        recall = hit / (recall_sum * 1.0)\n",
    "        return precision, recall\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_test():\n",
    "    \"\"\"\n",
    "    K则验证precision，recall求平均\n",
    "    \"\"\"\n",
    "    item_cf = ItemCF()\n",
    "    epoch = 5\n",
    "    pre_list = []\n",
    "    rec_list = []\n",
    "    for k in range(epoch):\n",
    "        item_cf.load_data(train_size=0.8)\n",
    "        item_cf.items_similarity(normalize=True)\n",
    "        pre, rec = item_cf.evaluate()\n",
    "        pre_list.append(pre)\n",
    "        rec_list.append(rec)\n",
    "    print(np.mean(pre_list), np.mean(rec_list))\n",
    "\n",
    "evaluate_test()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loaded data finish...\n",
      "computing all deviations...\n",
      "computed all deviations finish...\n",
      "calculating MAE and RMSE...\n",
      "calculating 100 users\n",
      "calculating 200 users\n",
      "calculating 300 users\n",
      "calculating 400 users\n"
     ]
    }
   ],
   "source": [
    "itemcf = ItemCF()\n",
    "itemcf.load_data(train_size=0.9)\n",
    "itemcf.items_similarity(normalize=True)\n",
    "mae, rmse = itemcf.validate()\n",
    "print(mae, rmse)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pre, rec = itemcf.evaluate()\n",
    "print(pre, rec)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = itemcf."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
